// @HEADER
//*********************************************************************//
//  SiYuan: A numerical PDE solver                                     //
//  Copyright (2022) YUAN Xi                                           //
//  This Software is released under the BSD 2-Clause license detailed  //
//  in the file "LICENSE" in the top-level SiYuan directory            //
//*********************************************************************//
// @HEADER

#include "Teuchos_ConfigDefs.hpp"
#include "Teuchos_RCP.hpp"
#include "Teuchos_TimeMonitor.hpp"
#include "Teuchos_DefaultComm.hpp"
#include "Teuchos_CommHelpers.hpp"
#include "Teuchos_CommandLineProcessor.hpp"
#include "Teuchos_XMLParameterListHelpers.hpp"
#include "Teuchos_YamlParameterListHelpers.hpp"
#include "Teuchos_FancyOStream.hpp"
#include "Teuchos_oblackholestream.hpp"
#include "Teuchos_Assert.hpp"
#include "Tpetra_Core.hpp"

#include "Panzer_NodeType.hpp"

#include "PanzerAdaptersSTK_config.hpp"
#include "Panzer_ClosureModel_Factory_TemplateManager.hpp"
#include "Panzer_PauseToAttach.hpp"
#include "Panzer_String_Utilities.hpp"
#include "Panzer_TpetraLinearObjContainer.hpp"

#include "ModelEvaluatorFactory.hpp"
#include "ClosureModel_Factory_TemplateBuilder.hpp"
#include "EquationSetFactory.hpp"
#include "BCStrategy_Factory.hpp"

#include <Ioss_SerializeIO.h>

#include <string>
#include <iostream>


int main(int argc, char *argv[])
{
  Tpetra::ScopeGuard tpetraScope(&argc, &argv);
  Teuchos::RCP<const Teuchos::Comm<int> > comm = Tpetra::getDefaultComm ();
  const int myRank = comm->getRank ();
  const int numProcs = comm->getSize ();
  if (myRank == 0) {
    std::cout << "Total number of processes: " << numProcs << std::endl;
#if defined(_OPENMP)
    std::cout << "Total number of threads per process: " << omp_get_max_threads() << std::endl;
#endif
  }

  Teuchos::RCP<Teuchos::FancyOStream> out = Teuchos::rcp(new Teuchos::FancyOStream(Teuchos::rcp(&std::cout,false)));
  if (numProcs > 1) {
    out->setShowProcRank(true);
    out->setOutputToRootOnly(0);
  }

  bool TimerOn;
  int status = 0;
  try {
    
    Teuchos::RCP<Teuchos::Time> total_time = 
      Teuchos::TimeMonitor::getNewTimer("SiYuan: Total Time");
    total_time -> start();
    
    Teuchos::TimeMonitor timer(*total_time); 
    
    // Parse the command line arguments
    std::string input_file_name = "SiYuan.xml";
    int exodus_io_num_procs = 0;
    bool pauseToAttachOn = false;
    {
      Teuchos::CommandLineProcessor clp;
      
      clp.setOption("i", &input_file_name, "Input xml/yaml filename");
      clp.setOption("exodus-io-num-procs", &exodus_io_num_procs, "Number of processes that can access the file system at the same time to read their portion of a sliced exodus file in parallel.  Defaults to 0 - implies all processes for the run can access the file system at the same time.");
      clp.setOption("pause-to-attach","disable-pause-to-attach", &pauseToAttachOn, "Call pause to attach, default is off.");
      
      Teuchos::CommandLineProcessor::EParseCommandLineReturn parse_return = clp.parse(argc,argv,&std::cerr);
      
      TEUCHOS_TEST_FOR_EXCEPTION(parse_return != Teuchos::CommandLineProcessor::PARSE_SUCCESSFUL, 
			 std::runtime_error, "Failed to parse command line!");
    }

    if(pauseToAttachOn)
       panzer::pauseToAttach();

    TEUCHOS_ASSERT(exodus_io_num_procs <= comm->getSize());
    if (exodus_io_num_procs != 0)
      Ioss::SerializeIO::setGroupFactor(exodus_io_num_procs);

    // Parse the input file and broadcast to other processes
    Teuchos::RCP<Teuchos::ParameterList> input_params = Teuchos::rcp(new Teuchos::ParameterList("SiYuan Parameters"));
	  auto p = strrchr(input_file_name.c_str(),'.');
   	if( p==NULL ) {
      std::cout << "Input file unknown! Please use yaml or xml file." << std::endl;
		  return -1;
    }
	  auto const input_extension = std::string( strrchr(input_file_name.c_str(),'.') );
	  if (input_extension == ".yaml" || input_extension == ".yml") {
        Teuchos::updateParametersFromYamlFileAndBroadcast(
            input_file_name, input_params.ptr(), *comm);
    } else if (input_extension == ".xml" ) {
        Teuchos::updateParametersFromXmlFileAndBroadcast(
            input_file_name, input_params.ptr(), *comm);
    } else {
      std::cout << "Input file unknown! Please use yaml or xml file." << std::endl;
		  return -1;
    }
    //Teuchos::writeParameterListToYamlFile (*input_params, "out.yaml");
    // *out << *input_params << std::endl;

    TimerOn = input_params->sublist("Debug Options").get<bool>("Time Monitor", false);
        
    // Add in the application specific equation set factory
    Teuchos::RCP<SiYuan::EquationSetFactory> eqset_factory = Teuchos::rcp(new SiYuan::EquationSetFactory);

    // Add in the application specific closure model factory
    SiYuan::ClosureModelFactory_TemplateBuilder cm_builder;
    panzer::ClosureModelFactory_TemplateManager<panzer::Traits> cm_factory;  
    cm_factory.buildObjects(cm_builder);

    // Add in the application specific bc factory
    SiYuan::BCFactory bc_factory; 

    // Create the global data
    Teuchos::RCP<panzer::GlobalData> global_data = panzer::createGlobalData();
    if( input_params->isSublist("Global Data") ) {
      Teuchos::ParameterList & global_params     = input_params->sublist("Global Data");
      for (Teuchos::ParameterList::ConstIterator entry = global_params.begin(); entry != global_params.end(); ++entry) {
        double dummy_type;
        auto key = entry->first;
        auto val = entry->second.getValue(&dummy_type);
        global_data->constants.emplace(key,val);
      }
    }

    // A GlobalStatistics closure model requires the comm to be set in the user data.
    input_params->sublist("User Data").set("Comm", comm);

    Teuchos::RCP<Thyra::ModelEvaluator<double> > physics;
    Teuchos::RCP<Thyra::ModelEvaluator<double> > piro;
    Teuchos::RCP<panzer::ResponseLibrary<panzer::Traits> > rLibrary;
    std::vector<Teuchos::RCP<panzer::PhysicsBlock> > physicsBlocks;
    Teuchos::RCP<panzer::LinearObjFactory<panzer::Traits> > linObjFactory;
    SiYuan::ModelEvaluatorFactory<double> me_factory;
    {
      me_factory.setParameterList(input_params);
      me_factory.buildObjects(comm,global_data,eqset_factory,bc_factory,cm_factory);
 
      // enusre all the responses are built
      me_factory.buildResponses(cm_factory);

      physicsBlocks = me_factory.getPhysicsBlocks();
      physics = me_factory.getPhysicsModelEvaluator();
      rLibrary = me_factory.getResponseLibrary();
      linObjFactory = me_factory.getLinearObjFactory();
      

      // Add in the application specific observer factories
      {
        // see if field coordinates are required, if so reset the workset container
        // and set the coordinates to be associated with a field in the mesh
        bool useCoordinateUpdate = false;
        for(std::size_t p=0;p<physicsBlocks.size();p++) {
           if(physicsBlocks[p]->getCoordinateDOFs().size()>0) {
              useCoordinateUpdate = true;
              break;
           }
        }
        piro = me_factory.buildControlModelEvaluator(physics,global_data);
      } 
      // setup outputs to mesh on the stkIOResponseLibrary
      me_factory.buildIOResponses(cm_factory, input_params);
      // setup outputs to mesh on the fluxResponseLibrary
      me_factory.buildFluxResponses(cm_factory, input_params);
    }
     
    // solve the system
    {
      // Set inputs
      Thyra::ModelEvaluatorBase::InArgs<double> inArgs = piro->createInArgs();
      const Thyra::ModelEvaluatorBase::InArgs<double> inArgsNominal = piro->getNominalValues();

      // Set outputs
      Thyra::ModelEvaluatorBase::OutArgs<double> outArgs = piro->createOutArgs();

      // Solution vector is returned as extra respons vector
      Teuchos::RCP<Thyra::VectorBase<double> > gx = Thyra::createMember(*physics->get_x_space());
      for(int i=0;i<outArgs.Ng()-1;i++)
         outArgs.set_g(i,Teuchos::null);
      outArgs.set_g(outArgs.Ng()-1,gx);

      // Now, solve the problem and return the responses
      piro->evalModel(inArgs, outArgs);

      me_factory.evalResponses(gx);
    }

    total_time -> stop();
    std::cout << "Total Elapsed Time = " << total_time->totalElapsedTime() << std::endl;
  }
  catch (std::exception& e) {
    *out << "*********** Caught Exception: Begin Error Report ***********" << std::endl;
    *out << e.what() << std::endl;
    *out << "************ Caught Exception: End Error Report ************" << std::endl;
    status = -1;
  }
  catch (std::string& msg) {
    *out << "*********** Caught Exception: Begin Error Report ***********" << std::endl;
    *out << msg << std::endl;
    *out << "************ Caught Exception: End Error Report ************" << std::endl;
    status = -1;
  }
  catch (...) {
    *out << "*********** Caught Exception: Begin Error Report ***********" << std::endl;
    *out << "Caught UNKOWN exception" << std::endl;
    *out << "************ Caught Exception: End Error Report ************" << std::endl;
    status = -1;
  }
  
  if( TimerOn ) Teuchos::TimeMonitor::summarize(*out,false,true,false);

  if (status == 0)
    *out << "SiYuan run completed." << std::endl;

  return status;
}
